﻿import mx.services.WebService;
import mx.services.Log;
import mx.xpath.XPathAPI;
//import mx.services.SOAPCall;


import net.spreadshirt.logger.Logger;

/**
 * The Service class provides the model (the Shop class) with an interface
 * to the webservice to send and load various data.
 * 
 * Every operation consists of two methods the first creates a webservice
 * result object prepared with the specific webservice operation and the
 * required paramters - the second one will call back some method of any
 * object and returns, in most cases, a complex data object.
 * 
 * The call back procedure needs an object with two properties:
 * the first one is called 'obj' an contains a reference to any object
 * and the second property stores the name of the object's method
 * to receive the webservice's response data.
 */
/* 
 * TODO
 * This class is quite brittle because of the way it recursively parses 
 * xml. It would probably be better to implement this using the 
 * XPathAPI.
*/

class net.spreadshirt.shop.Service {

	// The web service instance.
	private var _service:WebService;

	// Some required shop values.
	private var _shop_id:Number;
	private var _shop_lang:String;
	private var _shop_locale:String;

	// The session variable.
	private var _token:String;

	// The main static URLs.
	//private static var _BASE_URL:String = "http://www.spreadshirt.net/";
	private static var _BASE_URL:String = "http://localhost/Spreadshop/spreadshop.org/examples/flashShop/";
	//private static var _BASE_URL:String = "http://spreadshop.org/examples/flashShop/";
	
	//private static var _WSDL_URL:String = _BASE_URL +"services.php?wsdl";
	private static var _WSDL_URL:String = _BASE_URL +"services.xml";
	private static var _ORDER_URL:String = "http://www.spreadshirt.net/shop.php"
		+"?op=basket"
		+"&sid=[sid]" // "[sid]" will be replaced by ShopID
		+"&token=[token]"; // "[token]" will be replaced by SessionID

	/**
	 * Webservice Constructor
	 */
	public function Service(
		shopID:Number,
		langCode:String,
		localeCode:String
	) {
		// Stores the shop ID and lang & locale codes.
		_shop_id = shopID;
		_shop_lang = langCode;
		_shop_locale = localeCode;
		// Creates a Log object which records all events
		// when calling a web service.
		var log:Log = new Log(Log.VERBOSE);
		log.onLog = function(msg:String):Void {
			Logger.getLog().debug(msg);
		};
		// Creates the WebService instance.
		_service = new WebService(_WSDL_URL, log);
	}

	/**
	 * Opens the shop website to submit the order information.
	 */
	public function submitOrder():Void {
		var token:String = getToken();
		var sid:String = String(_shop_id);
		var url:String = _ORDER_URL;
		url = url.split("[sid]").join(sid);
		url = url.split("[token]").join(token);
		getURL(url, "_blank");
	}

	/**
	 * Gets the token string.
	 * 
	 * @return	The token string.
	 */
	public function getToken():String {
		return _token;
	}

	/**
	 * Sets the token string.
	 * 
	 * @param	token	The token string.
	 */
	private function setToken(token:String):Void {
		_token = token;
	}

	/**
	 * Initialize session - receive token.
	 */
	public function initializeSession(callback:Object):Object {
		var wsResultObj:Object = setupWSResultObj(
			_service["initialize_session"](
				_shop_id
			),
			callback
		);
		wsResultObj.service = this;
		wsResultObj.onResult = function(token:String):Void {
			this.service.setToken(token);
			this.callback.obj[this.callback.fnc](token);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[initialize_session]:\n"
				+"\t\ttoken:\t\t\t"+ token +"\n";
			Logger.getLog().debug(strDebug);
		};
		return wsResultObj;
	};

	/**
	 * Update session - set language and local code.
	 */
	public function updateSession(callback:Object):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["update_session"](
				_shop_id,
				_shop_lang,
				_shop_locale
			),
			callback
		);
		wsResultObj.lang = _shop_lang;
		wsResultObj.locale = _shop_locale;
		wsResultObj.service = this;
		wsResultObj.onResult = function(token:String):Void {
			this.service.setToken(token);
			this.callback.obj[this.callback.fnc](
				token,
				this.lang,
				this.locale
			);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[update_session]:\n"
				+"\ttoken: \t\t\t"+ token
				+"\tlang: \t\t\t"+ this.lang
				+"\tlocale: \t\t\t"+ this.locale;
			Logger.getLog().debug(strDebug);
		};
	}
/*##############################################################################################
################################################################################################ */
	/**
	 * Request all available articles.
	 */
	public function getArticles(
		callback:Object,
		producttype_id:Number,
		design_id:Number,
		position_id:Number,
		limit:Number,
		search_phrase:String
	):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["get_articles"](
				_token,
				_shop_id,
				producttype_id,
				design_id,
				position_id,
				limit,
				search_phrase
			),
			callback
		);
		wsResultObj.doDecoding=false;

		wsResultObj.onResult = function(result:Object):Void {
			/* 
			*  Flash is not decoding the result properly for some reason.
			*  One way around this is to set doDecoding to false (above)
			*  and then make a new XML object with the raw XML.
			*  
			*  An alternative is set result to the 
			*  PendingCall.response property.
			*  result = this.response;
			*/
			result = new XML(result.toString());
			result = result.childNodes[0].childNodes[0];
			//trace(result.childNodes);
			var items:Array = new Array();
			for(var n:Number = 0; n < result.childNodes.length; n++) {
				var data:XMLNode = result.childNodes[n].childNodes[1];
				// Parse design_ids.
				var dsgs:Array = this.getNodeValues(
					data,
					"design_ids"
				);
				var design_ids:Array = new Array();
				for(var i:Number = 0; i < dsgs.length; i++) {
					design_ids.push(parseInt(dsgs[i].firstChild.nodeValue));
				}
				// Parse producttype sizes.
				var psizs:Array = this.getNodeValues(
					data,
					"producttype_sizes"
				);
				var sizes:Array = new Array();
				for(var i:Number = 0; i < psizs.length; i++) {
					/**
					 *  Using getNodeValues won't work in this case where
					 *  the xml is using key/value element pairs. In fact
					 *  I'm not exactly sure what it was supposed to do since
					 *  all that looping doesn't seem to achieve much.
					 *  The solution below is still *very* brittle however.
					 */
					var pt_size_id:String = new String();
					var pt_weight:String = new String();
					var pt_name:String = new String();
					pt_size_id = psizs[i].childNodes[1].childNodes[0].childNodes[1].firstChild.nodeValue;
					pt_weight  = psizs[i].childNodes[1].childNodes[1].childNodes[1].firstChild.nodeValue;
					pt_name    = psizs[i].childNodes[1].childNodes[2].childNodes[1].firstChild.nodeValue;
					
					sizes.push({
						producttype_size_id: parseInt(pt_size_id),
						weight: parseInt(pt_weight),
						name: this.fixString(pt_name)
					});
				}
				// Creates an object to store all item values.
				var item:Object = {
					article_id: parseInt(this.getNodeValues(
						data,
						"article_id"
					)),
					product_id: parseInt(this.getNodeValues(
						data,
						"product_id"
					)),
					producttype_id: parseInt(this.getNodeValues(
						data,
						"producttype_id"
					)),
					producttype_color_id: parseInt(this.getNodeValues(
						data,
						"producttype_color_id"
					)),
					partner_id: parseInt(this.getNodeValues(
						data,
						"partner_id"
					)),
					shop_id: parseInt(this.getNodeValues(
						data,
						"shop_id"
					)),
					currency: this.fixString(this.getNodeValues(
						data,
						"currency"
					)),
					gross: parseFloat(this.getNodeValues(
						data,
						"gross"
					)),
					vat: parseFloat(this.getNodeValues(
						data,
						"vat"
					)),
					name: this.fixString(this.getNodeValues(
						data,
						"name"
					)),
					description: this.fixString(this.getNodeValues(
						data,
						"description"
					)),
					image_url: fixURL(this.fixString(this.getNodeValues(
						data,
						"image_url"
					))),
					base_url: this.fixString(this.getNodeValues(
						data,
						"base_url"
					)),
					view_count: parseInt(this.getNodeValues(
						data,
						"view_count"
					)),
					design_ids: design_ids,
					producttype_sizes: sizes
				};
				items.push(item);
			}
			this.callback.obj[this.callback.fnc](items);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[get_articles]:\n";
			for(var n:Number = 0; n < items.length; n++) {
				var item:Object = items[n];
				var strSizes:String = new String();
				var sizes:Array = item.producttype_sizes;
				for(var i:Number = 0; i < sizes.length; i++) {
					var itm:Object = sizes[i];
					var str:String = ""
						+"\t\t[ "+ i +" ]\n"
						+"\t\t\t\tsize_id:\t\t\t"+ itm.producttype_size_id
						+"\n"
						+"\t\t\t\tweight:\t\t\t\t"+ itm.weight +"\n"
						+"\t\t\t\tname:\t\t\t\t"+ itm.name +"\n";
					strSizes += str;
				}
				var strImages:String = new String();
				for(var i:Number = 0; i < item.images.length; i++) {
					var itm:Object = item.images[i];
					var str:String = "\t\t\t\t"+ itm +"\n";
					strImages += str;
				}
				strDebug += ""
					+"["+ n +"]\n"
					+"\t\tarticle_id:\t\t\t\t\t"+ item.article_id +"\n"
					+"\t\tproduct_id:\t\t\t\t\t"+ item.product_id +"\n"
					+"\t\tproducttype_id:\t\t\t\t\t"+ item.producttype_id
					+"\n"
					+"\t\tproducttype_color_id:\t\t\t\t"
						+ item.producttype_color_id +"\n"
					+"\t\tpartner_id:\t\t\t\t\t"+ item.partner_id +"\n"
					+"\t\tshop_id:\t\t\t\t\t"+ item.shop_id +"\n"
					+"\t\tcurrency:\t\t\t\t\t"+ item.currency +"\n"
					+"\t\tgross:\t\t\t\t\t\t"+ item.gross +"\n"
					+"\t\tvat:\t\t\t\t\t\t"+ item.vat +"\n"
					+"\t\tname:\t\t\t\t\t\t"+ item.name +"\n"
					+"\t\tdescription:\t\t\t\t\t"
						+((item.description.length > 0)
							? item.description.split("\n").join(", ")
							: "-"
						 ) +"\n"
					+"\t\timage_url:\t\t\t\t\t"
						+item.image_url	+"\n"
					+"\t\tbase_url:\t\t\t\t\t"
						+item.base_url +"\n"
					+"\t\tview_count:\t\t\t\t\t"
						+item.view_count +"\n"
					+"\t\tdesign_ids:\t\t\t\t\t"
						+item.design_ids +"\n"
					+"\t\tproducttype_sizes: \n"+ strSizes +"\n";
			}
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Request single article.
	 *  Not sure if this is implemented correctly. I've applied changes
	 *  as with getArticles but this method doesn't seem to be used 
	 *  so I haven't had an opportunity to see if it works.
	 */
	public function getArticle(
		callback:Object,
		article_id:Number
	):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["get_article"](
				_token,
				article_id
			),
			callback
		);
		wsResultObj.doDecoding=false;

		wsResultObj.onResult = function(result:Object):Void {
			/* 
			*  Flash is not decoding the result properly for some reason.
			*  One way around this is to set doDecoding to false (above)
			*  and then make a new XML object with the raw XML.
			*  
			*  An alternative is set result to the 
			*  PendingCall.response property.
			*  result = this.response;
			*/
			result = new XML(result.toString());
			trace (result);
			// This line is taken from getArticles and is almost certainly wrong.
			// The xml nodes aren't as deep as with getArticles.
			result = result.childNodes[0].childNodes[0];
			var data:Object = result.parentNode.childNodes[0];
			// Parse design_ids.
			var dsgns:Array = this.getNodeValues(
				data,
				"design_ids"
			);
			var design_ids:Array = new Array();
			for(var i:Number = 0; i < dsgns.length; i++) {
				design_ids.push(parseInt(dsgns[i].firstChild.nodeValue));
			}
			// Parse producttype sizes.
			var psizs:Array = this.getNodeValues(
				data,
				"producttype_sizes"
			);
			var sizes:Array = new Array();
			for(var i:Number = 0; i < psizs.length; i++) {
					/**
					 *  Using getNodeValues won't work in this case where
					 *  the xml is using key/value element pairs. In fact
					 *  I'm not exactly sure what it was supposed to do since
					 *  all that looping doesn't seem to achieve much.
					 *  The solution below is still *very* brittle however.
					 */
					var pt_size_id:String = new String();
					var pt_weight:String = new String();
					var pt_name:String = new String();
					pt_size_id = psizs[i].childNodes[1].childNodes[0].childNodes[1].firstChild.nodeValue;
					pt_weight  = psizs[i].childNodes[1].childNodes[1].childNodes[1].firstChild.nodeValue;
					pt_name    = psizs[i].childNodes[1].childNodes[2].childNodes[1].firstChild.nodeValue;
					
					sizes.push({
						producttype_size_id: parseInt(pt_size_id),
						weight: parseInt(pt_weight),
						name: this.fixString(pt_name)
					});
			}
			// Creates an object to store all item values.
			var item:Object = {
				article_id: parseInt(this.getNodeValues(
					data,
					"article_id"
				)),
				product_id: parseInt(this.getNodeValues(
					data,
					"product_id"
				)),
				producttype_id: parseInt(this.getNodeValues(
					data,
					"producttype_id"
				)),
				producttype_color_id: parseInt(this.getNodeValues(
					data,
					"producttype_color_id"
				)),
				partner_id: parseInt(this.getNodeValues(
					data,
					"partner_id"
				)),
				shop_id: parseInt(this.getNodeValues(
					data,
					"shop_id"
				)),
				currency: this.fixString(this.getNodeValues(
					data,
					"currency"
				)),
				gross: parseFloat(this.getNodeValues(
					data,
					"gross"
				)),
				vat: parseFloat(this.getNodeValues(
					data,
					"vat"
				)),
				name: this.fixString(this.getNodeValues(
					data,
					"name"
				)),
				description: this.fixString(this.getNodeValues(
					data,
					"description"
				)),
				// fixURL is a temporary fix for a temporary Services problem
				image_url: fixURL(this.fixString(this.getNodeValues(
					data,
					"image_url"
				))),
				base_url: this.fixString(this.getNodeValues(
					data,
					"base_url"
				)),
				view_count: parseInt(this.getNodeValues(
					data,
					"view_count"
				)),
				design_ids: design_ids,
				producttype_sizes: sizes
			};
			this.callback.obj[this.callback.fnc](item);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[get_article]:\n";
			var strDebugSizes:String = new String();
			for(var i:Number = 0; i < item.producttype_sizes.length; i++) {
				var itm:Object = item.producttype_sizes[i];
				var str:String = ""
					+"\t\t[ "+ i +" ]\n"
					+"\t\t\t\tsize_id:\t\t\t"+ itm.producttype_size_id +"\n"
					+"\t\t\t\tweight:\t\t\t\t"+ itm.weight +"\n"
					+"\t\t\t\tname:\t\t\t\t"+ itm.name +"\n";
				strDebugSizes += str;
			}
			strDebug += ""
				+"\t\tarticle_id:\t\t\t\t\t"+ item.article_id +"\n"
				+"\t\tproduct_id:\t\t\t\t\t"+ item.product_id +"\n"
				+"\t\tproducttype_id:\t\t\t\t"+ item.producttype_id +"\n"
				+"\t\tproducttype_color_id:\t\t"
					+item.producttype_color_id +"\n"
				+"\t\tpartner_id:\t\t\t\t\t"+ item.partner_id +"\n"
				+"\t\tshop_id:\t\t\t\t\t"+ item.shop_id +"\n"
				+"\t\tcurrency:\t\t\t\t\t"+ item.currency +"\n"
				+"\t\tgross:\t\t\t\t\t\t"+ item.gross +"\n"
				+"\t\tvat:\t\t\t\t\t\t\t"+ item.vat +"\n"
				+"\t\tname:\t\t\t\t\t\t"+ item.name +"\n"
				+"\t\tdescription:\t\t\t\t"
					+((item.description.length > 0)
						? item.description.split("\n").join(", ")
						: "-"
					 ) +"\n"
				+"\t\timage_url:\t\t\t\t\t"
					+item.image_url +"\n"
				+"\t\timage_base_url:\t\t\t\t"
					+item.image_base_url +"\n"
				+"\t\timage_sizes:\t\t\t\t"
					+item.image_count +"\n"
				+"\t\tproducttype_sizes: \n"+ sizes +"\n";
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Adds an article to the basket.
	 */
	public function addBasketitem(
		callback:Object,
		article_id:Number,
		quantity:Number,
		producttype_size_id:Number,
		producttype_color_id:Number
	):Void {
		
		var wsResultObj:Object = setupWSResultObj(
			_service["add_basketitem"](
				_token,
				article_id,
				quantity,
				producttype_size_id,
				producttype_color_id
			),
			callback
		);
		//trace ("article_id: "+ article_id + "\nquantity: "+ quantity + "\nproducttype_size_id: " + producttype_size_id + "\nproducttype_color_id:" + producttype_color_id +"\n");
		wsResultObj.onResult = function(result:String):Void {
			var basketitem_id:Number = parseInt(result);
			this.callback.obj[this.callback.fnc](basketitem_id);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[add_basketitem]:\n"
				+"\t\tbasketitem_id:\t\t\t\t\t"+ basketitem_id +"\n";
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Updates basket item - modifies quantity, size or color
	 */
	public function updateBasketitem(
		callback:Object,
		basketitem_id:Number,
		quantity:Number,
		producttype_size_id:Number,
		producttype_color_id:Number
	):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["update_basketitem"](
				_token,
				basketitem_id,
				quantity,
				producttype_size_id,
				producttype_color_id
			),
			callback
		);
		wsResultObj.doDecoding = false;
		wsResultObj.onResult = function(xmlResult:XML):Void {
			// Decoding fix. See get_articles for details.
			xmlResult = new XML(xmlResult.toString());
			trace(xmlResult);
			var result:String = new String("49550")
			//result = result;
			
			var updated_basketitem_id:Number = parseInt(result);
			this.callback.obj[this.callback.fnc](updated_basketitem_id);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[update_basketitem]:\n"
				+"\tupdated_basketitem_id:\t\t\t\t"
				+ updated_basketitem_id +"\n";
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Request all basket items and all basket values.
	 */
	public function getBasket(callback:Object):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["get_basket"](
				_token
			),
			callback
		);
		//wsResultObj.doDecoding = false;
		wsResultObj.onResult = function(result:Object):Void {
			trace("RESULT.basketitems is XMLNode: "+XMLNode.prototype.isPrototypeOf(result.basketitems));
			//var data:Object = result.parentNode.childNodes[0];
			var data:Object = result;
			//trace("RESULT.parentNode.childNodes[0]: "+result.parentNode.childNodes[0]);
			//trace("DATA: "+data);
			for (var prop in result) { 
 				trace("result."+prop+" = "+result[prop]); 
			} 
			for (var prop in result.basketitems) { 
 				trace("Output of result.basketitems."+prop+" = "+result.basketitems[prop]); 
			} 
			
			// Get producttype sizes.
			//var bitms:Array = this.getNodeValues(data, "basketitems");
			var rawXML:XML = wsResultObj.response;
			var bitms:Array = XPathAPI.selectNodeList(rawXML.firstChild,"/*/*/*/return/basketitems/item");
			trace ("bitms " + bitms);
			trace("bitms.length " + bitms.length);
			var items:Array = new Array();
			for(var i:Number = 0; i < bitms.length; i++) {
				var item:Object = {
					basketitem_id: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/basketitem_id").firstChild.nodeValue)
					,
					article_id: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/article_id").firstChild.nodeValue)
					,
					product_id: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/product_id").firstChild.nodeValue)
					,
					quantity: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/quantity").firstChild.nodeValue)
					,
					shop_id: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/shop_id").firstChild.nodeValue)
					,
					producttype_id: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/producttype_id").firstChild.nodeValue)
					,
					producttype_size_id: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/producttype_size_id").firstChild.nodeValue)
					,
					producttype_color_id: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/producttype_color_id").firstChild.nodeValue)
					,
					currency: data.currency.toString()
					,
					gross: parseFloat(XPathAPI.selectSingleNode(bitms[i],"item/value/gross").firstChild.nodeValue)
					,
					vat: parseInt(XPathAPI.selectSingleNode(bitms[i],"item/value/vat").firstChild.nodeValue)
					,
					name: XPathAPI.selectSingleNode(bitms[i],"item/value/name").firstChild.nodeValue
					,
					image_url: fixURL(XPathAPI.selectSingleNode(bitms[i],"item/value/image_url").firstChild.nodeValue)
					,
					description: XPathAPI.selectSingleNode(bitms[i],"item/value/description").firstChild.nodeValue
				};
				for (var prop in item) { 
 				trace("item."+prop+" = "+item[prop]); 
				} 
				items.push(item);
			}
			// Creates an object to store all item values.
			var basket:Object = {
				gross_basket: parseFloat(
					data.gross_basket
				),
				gross_shipping: parseFloat(
					data.gross_shipping
				),
				gross_handling: parseFloat(
					data.gross_handling
				),
				gross_discount: parseFloat(
					data.gross_discount
				),
				gross_coupon: parseFloat(
					data.gross_coupon
				),
				gross_total: parseFloat(
					data.gross_total
				),
				vat_basket: parseFloat(
					data.vat_basket
				),
				vat_total: parseFloat(
					data.vat_total
				),
				currency: 
					data.currency.toString()
				,
				items: items
			};
			this.callback.obj[this.callback.fnc](basket);
			// DEBUG: show response
			var strDebug:String = new String();
			var strDebugItems:String = new String();
			strDebug = "WebService[get_basket]:\n";
			for(var i:Number = 0; i < basket.basketitems.length; i++) {
				var itm:Object = basket.basketitems[i];
				var str:String = ""
					+"\t\t[ "+ i +" ]\n"
					+"\t\t\t\tbasketitem_id:\t\t\t"  + itm.basketitem_id +"\n"
					+"\t\t\t\tarticle_id:\t\t\t\t\t" + itm.article_id +"\n"
					+"\t\t\t\tproduct_id:\t\t\t\t"   + itm.product_id +"\n"
					+"\t\t\t\tquantity:\t\t\t\t\t"   + itm.quantity +"\n"
					+"\t\t\t\tshop_id:\t\t\t\t\t"    + itm.shop_id +"\n"
					+"\t\t\t\tproducttype_id:\t\t\t" + itm.producttype_id
					+"\n"
					+"\t\t\t\tproducttype_size_id:\t\t"
													 + itm.producttype_size_id +"\n"
					+"\t\t\t\tproducttype_color_id:\t\t"
													 + itm.producttype_color_id +"\n"
					+"\t\t\t\tcurrency:\t\t\t\t\t"   + itm.currency +"\n"
					+"\t\t\t\tgross:\t\t\t\t\t\t"	 + itm.gross +"\n"
					+"\t\t\t\tvat:\t\t\t\t\t\t\t"	 + itm.vat +"\n"
					+"\t\t\t\tname:\t\t\t\t\t\t"	 + itm.name +"\n"
					+"\t\t\t\timage_url:\t\t\t\t\t"	 + itm.image_url +"\n"
					+"\t\t\t\tdescription:\t\t\t\t"
						+((itm.description.length > 0)
							? itm.description.split("\n").join(", ")
							: "-"
						 ) +"\n";
				strDebugItems += str;
			}
			strDebug += ""
				+"\t\tgross_basket:\t\t"  + basket.gross_basket +"\n"
				+"\t\tgross_shipping:\t\t"+ basket.gross_shipping +"\n"
				+"\t\tgross_handling:\t\t"+ basket.gross_handling +"\n"
				+"\t\tgross_discount:\t\t"+ basket.gross_discount +"\n"
				+"\t\tgross_coupon:\t\t"  + basket.gross_coupon +"\n"
				+"\t\tgross_total:\t\t"   + basket.gross_total +"\n"
				+"\t\tvat_basket:\t\t"    + basket.vat_basket +"\n"
				+"\t\tvat_total:\t\t"     + basket.vat_total +"\n"
				+"\t\tcurrency:\t\t"      + basket.currency +"\n"
				+"\t\tbasketitems:\n"     + strDebugItems;
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Use coupon code.
	 */
	public function redeemCoupon(
		callback:Object,
		coupon_code:String
	):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["redeem_coupon"](
				_token,
				coupon_code
			),
			callback
		);
		wsResultObj.onResult = function(result:String):Void {
			var isOk:Boolean = Boolean(result);
			Logger.getLog().error(" --- redeem_coupon :"
				+" response --- \n\n"+ result);
			this.callback.obj[this.callback.fnc](isOk);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[redeem_coupon]:\n"
				+"\t\tresult:\t\t\t"+ result +"\n";
			Logger.getLog().debug(strDebug);
		};
	};

	/**
	 * Receive terms and conditions text.
	 */
	public function getTNC(callback:Object):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["get_tnc"](
				_token
			),
			callback
		);
		wsResultObj.onResult = function(result:String):Void {
			var str:String = String(result);
			this.callback.obj[this.callback.fnc](str);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[get_tnc]:\n"
				+"\t\ttnc text:\t\t"+ str.substr(1, 100) +" ...\n";
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Accept terms and conditions.
	 */
	public function acceptTNC(callback:Object):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["accept_tnc"](
				_token
			),
			callback
		);
		wsResultObj.onResult = function(result:String):Void {
			var accepted:Boolean = Boolean(result);
			this.callback.obj[this.callback.fnc](accepted);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[accept_tnc]:\n"
				+"\t\taccepted:\t\t\t"+ accepted +"\n";
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Request all design items.
	 */
	public function getDesigns(
		callback:Object,
		producttype_id:Number,
		position:Number,
		limit:Number,
		search_phrase:String
	):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["get_designs"](
				_token,
				_shop_id,
				producttype_id,
				position,
				limit,
				search_phrase
			),
			callback
		);
		wsResultObj.onResult = function(result:Object):Void {
			var items:Array = new Array();
			//trace("HERE'S THE GET DESIGNS RESULT: "+result);
			for(var n:Number = 0; n < result.childNodes.length; n++) {
				// Creates an object to store all item values.
				var data:XMLNode = result.childNodes[n].childNodes[1];
				/*var str:String = new String();
				str = String(data);
				Logger.getLog().debug(str);*/
				/*##########################################################################*/
				var item:Object = {
					design_id: parseInt(this.getNodeValues(
						data,
						"design_id"
					)),
					partner_id: parseInt(this.getNodeValues(
						data,
						"partner_id"
					)),
					weight: parseInt(this.getNodeValues(
						data,
						"weight"
					)),
					name: this.fixString(this.getNodeValues(
						data,
						"name"
					)),
					image_url: this.fixString(this.getNodeValues(
						data,
						"image_url"
					))
				};
				items.push(item);
			}
			this.callback.obj[this.callback.fnc](items);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[get_designs]:\n";
			for(var i:Number = 0; i < items.length; i++) {
				var item:Object = items[i];
				var str:String = ""
				+"["+ i +"]\n"
				+"\t\tdesign_id:\t\t"+ item.design_id +"\n"
				+"\t\tpartner_id:\t\t"+ item.partner_id +"\n"
				+"\t\tweight:\t\t\t"+ item.weight +"\n"
				+"\t\tname:\t\t\t"+ item.name +"\n"
				+"\t\timage_url:\t\t"+ item.image_url +"\n";
				strDebug += str;
			}
			Logger.getLog().debug(strDebug);
		};
	}


	/**
	 * Request all product types.
	 */
	public function getProducttypes(
		callback:Object,
		position:Number,
		limit:Number,
		search_phrase:String
	):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["get_producttypes"](
				_token,
				_shop_id,
				position,
				limit,
				search_phrase
			),
			callback
		);
		wsResultObj.onResult = function(result:Object):Void {
			/*trace("________________________________________________________________"
			   +"\n|                                                              |"
			   +"\n|______________________________________________________________|");*/
			var items:Array = new Array();
			var rawXML:XML = wsResultObj.response;
			var xmlNodes:Array = XPathAPI.selectNodeList(rawXML.firstChild,"/*/*/*/return/item");
			
			for(var n:Number = 0; n < xmlNodes.length; n++) {
				var data:XMLNode = xmlNodes[n];
				
				var ptcols:Array = XPathAPI.selectNodeList(data, "/*/value/producttype_colors/item");
				var colors:Array = new Array();
				for(var i:Number = 0; i < ptcols.length; i++) {
					colors.push({
						producttype_color_id: parseInt(
							XPathAPI.selectSingleNode(ptcols[i], "item/value/item[key='producttype_color_id']").firstChild.nextSibling.firstChild.nodeValue
						),
						weight: parseInt(
							/*this.getNodeValues(
							ptcols[i],
							"item/weight"
						)*/
							XPathAPI.selectSingleNode(ptcols[i], "item/value/item[key='weight']").firstChild.nextSibling.firstChild.nodeValue
							),
						name: this.fixString(
							XPathAPI.selectSingleNode(ptcols[i], "item/value/item[key='name']").firstChild.nextSibling.firstChild.nodeValue 
							),
						color_icon_url: this.fixString(
							XPathAPI.selectSingleNode(ptcols[i], "item/value/item[key='color_icon_url']").firstChild.nextSibling.firstChild.nodeValue   
							),
						image_url: this.fixString(
							XPathAPI.selectSingleNode(ptcols[i], "item/value/item[key='image_url']").firstChild.nextSibling.firstChild.nodeValue  
							)
					});
				}
				
				// Get producttype sizes.
				/*var psizs:Array = this.getNodeValues(
					data,
					"producttype_sizes"
				);*/
				var psizs:Array = XPathAPI.selectNodeList(data, "/*/value/producttype_sizes/item");
				var sizes:Array = new Array();
				for(var i:Number = 0; i < psizs.length; i++) {
					sizes.push({
						producttype_size_id: parseInt(
						/*this.getNodeValues(
							psizs[i],
							"item/producttype_size_id"
						)*/
							XPathAPI.selectSingleNode(psizs[i], "item/value/item[key='producttype_size_id']").firstChild.nextSibling.firstChild.nodeValue  
						),
						weight: parseInt(
							XPathAPI.selectSingleNode(psizs[i], "item/value/item[key='weight']").firstChild.nextSibling.firstChild.nodeValue 
						),
						name: this.fixString(
							XPathAPI.selectSingleNode(psizs[i], "item/value/item[key='name']").firstChild.nextSibling.firstChild.nodeValue 
						)
					});
				}
				// Creates an object to store all item values.
				var item:Object = {
					producttype_id: parseInt(
						XPathAPI.selectSingleNode(data, "/*/value/producttype_id").firstChild.nodeValue  
					),
					producttype_category_id: parseInt(
						XPathAPI.selectSingleNode(data, "/*/value/producttype_category_id").firstChild.nodeValue
					),
					producttype_brand_id: parseInt(
						XPathAPI.selectSingleNode(data, "/*/value/producttype_brand_id").firstChild.nodeValue
					),
					producttype_colors: colors,
					producttype_sizes: sizes,
					delivery_period: this.fixString(
						XPathAPI.selectSingleNode(data, "/*/value/delivery_period").firstChild.nodeValue
					),
					name: this.fixString(
						XPathAPI.selectSingleNode(data, "/*/value/name").firstChild.nodeValue
					),
					description_short: this.fixString(
						XPathAPI.selectSingleNode(data, "/*/value/description_short").firstChild.nodeValue
					),
					description: this.fixString(
						XPathAPI.selectSingleNode(data, "/*/value/description").firstChild.nodeValue
					)
				};
				items.push(item);
			}
			this.callback.obj[this.callback.fnc](items);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[get_producttypes]:\n";
			for(var n:Number = 0; n < items.length; n++) {
				var item:Object = items[n];
				var strDebugColors:String = new String();
				var colors:Array = item.producttype_colors;
				for(var i:Number = 0; i < colors.length; i++) {
					var itm:Object = colors[i];
					var str:String = ""
						+"\t\t[ "+ i +" ]\n"
						+"\t\t\t\tproducttype_color_id:\t\t\t"
							+itm.producttype_color_id +"\n"
						+"\t\t\t\tweight:\t\t\t\t\t"+ itm.weight +"\n"
						+"\t\t\t\tname:\t\t\t\t\t"+ itm.name +"\n"
						+"\t\t\t\tcolor_icon_url:\t\t\t\t"+ itm.color_icon_url
						+"\n"
						+"\t\t\t\timage_url:\t\t\t\t"+ itm.image_url +"\n";
					strDebugColors += str;
				}
				var strDebugSizes:String = new String();
				var sizes:Array = item.producttype_sizes;
				for(var i:Number = 0; i < sizes.length; i++) {
					var itm:Object = sizes[i];
					var str:String = ""
						+"\t\t[ "+ i +" ]\n"
						+"\t\t\t\tproducttype_size_id:\t\t\t"
							+itm.producttype_size_id +"\n"
						+"\t\t\t\tweight:\t\t\t\t\t"+ itm.weight +"\n"
						+"\t\t\t\tname:\t\t\t\t\t"+ itm.name +"\n";
					strDebugSizes += str;
				}
				strDebug += ""
					+"[ "+ n +" ]\n"
					+"\t\tproducttype_id:\t\t\t\t"+ item.producttype_id
					+"\n"
					+"\t\tproducttype_category_id:\t\t"
						+item.producttype_category_id +"\n"
					+"\t\tproducttype_brand_id:\t\t\t"
						+item.producttype_brand_id +"\n"
					+"\t\tproducttype_colors: \n"+ strDebugColors
					+"\t\tproducttype_sizes: \n"+ strDebugSizes
					+"\t\tdelivery_period:\t\t\t\t"+ item.delivery_period
					+"\n"
					+"\t\tname:\t\t\t\t\t\t"+ item.name +"\n"
					+"\t\tdescription_short:\t\t\t\t"
						+((item.description_short.length > 0)
							? item.description_short
							: "-"
						 ) +"\n"
					+"\t\tdescription:\t\t\t\t\t"
						+item.description.split("\n").join(", ") +"\n";
			}
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Request all category items of every product type.
	 */
	public function getProducttypeCategories(
		callback:Object,
		position:Number,
		limit:Number,
		search_phrase:String
	):Void {
		var wsResultObj:Object = setupWSResultObj(
			_service["get_producttype_categories"](
				_token,
				_shop_id,
				position,
				limit,
				search_phrase
			),
			callback
		);
		wsResultObj.doDecoding = false;
		wsResultObj.onResult = function(result:Object):Void {
			result = new XML(result.toString());
			//trace(   result.childNodes[0].childNodes[0]);
			result = result.childNodes[0].childNodes[0];
			//trace (result.childNodes);
			var items:Array = new Array();
			for(var n:Number = 0; n < result.childNodes.length; n++) {
				// Creates an object to store all item values.
				var data:XMLNode = result.childNodes[n].childNodes[1];
				var item:Object = {
					producttype_category_id: parseInt(this.getNodeValues(
						data,
						"producttype_category_id"
					)),
					active: parseInt(this.getNodeValues(
						data,
						"active"
					)),
					weight: parseInt(this.getNodeValues(
						data,
						"weight"
					)),
					name: this.fixString(this.getNodeValues(
						data,
						"name"
					)),
					name_plural: this.fixString(this.getNodeValues(
						data,
						"name_plural"
					))
				};
				items.push(item);
			}
			this.callback.obj[this.callback.fnc](items);
			// DEBUG: show response
			var strDebug:String = new String();
			strDebug = "WebService[get_producttype_categories]:\n";
			for(var n:Number = 0; n < items.length; n++) {
				var item:Object = items[n];
				strDebug += ""
					+"["+ n +"]\n"
					+"\t\tproducttype_category_id:\t"
						+item.producttype_category_id +"\n"
					+"\t\tactive:\t\t\t\t"+ item.active +"\n"
					+"\t\tweight:\t\t\t\t"+ item.weight +"\n"
					+"\t\tname:\t\t\t\t"+ item.name +"\n"
					+"\t\tname_plural:\t\t\t"+ item.name_plural +"\n";
			}
			Logger.getLog().debug(strDebug);
		};
	}

	/**
	 * Prepares the webservice result object.
	 * Attaches a generalized onFault() method and also the getNodeValues()
	 * and fixString() functions - and the callback object is looped through.
	 * 
	 * @param	wsObj	A webservice operation object.
	 * @param	callback	The callback object.
	 *
	 * @return	the prepared webservice result object
	 */
	private static function setupWSResultObj(
		wsObj:Object,
		callback:Object
	):Object {
		var wsResultObj:Object = wsObj;
		wsResultObj.onFault = function(fault:Object):Void {
			var code:String = fault.faultcode;
			var strg:String = fault.faultstring;
			var log:String = (code == "Server.Connection")
				? "fatal"
				: "error"
			;
			Logger.getLog()[log](code + ", " + strg);
		};
		wsResultObj.onResult = function(result:String):Void {
		};
		wsResultObj.onComplete = function(result:String):Void {
		};
		wsResultObj.getNodeValues = getNodeValues;
		wsResultObj.fixString = fixString;
		wsResultObj.callback = callback;
		return wsResultObj;
	}

	/**
	 * Simple function for easier handling xml nodes.
	 */
	private static function getNodeValues(data:XMLNode, nodes:String):XMLNode {
		var path:Array = nodes.split("/");
		var values:XML;
		for(var p:Number = 0; p < path.length; p++) {
			for(var i:Number = 0; i < data.childNodes.length; i++) {
				/*
				 *  Shouldn't this be appending a childNodes property
				 *  for each element of path array or else recursively calling itself
				 *  for each element of the path array?
				 *  I don't really see what's intended here but it doesn't
				 *  seem to work for anything beyond the least level of
				 *  complexity. This function should probably be changed
				 *  to use XPath.
				 */
				if(data.childNodes[i].nodeName == path[p]) {
					return data.childNodes[i].childNodes;
				}
			}
		}
	}

	/**
	 * Simple function to fix some strings.
	 */
	private static function fixString(arg:String):String {
		var str:String = String(arg); 
		var out:String = (str.indexOf("&amp;amp;") > -1)
			? str.split("&amp;amp;").join("&")
			: str.split("&amp;").join("&")
		;
		out = out.split("&quot;").join("\"");
		out = out.split("&apos;").join("'");
		return out;
	}
	
	/**
	 * Simple fix for an error with image_url property in get_article and get_articles functions
	 * The image_url is being prepended  with "http://www.spreadshirt.net/"
	 * This method simply strips that off.
	 */
	 private static function fixURL(arg:String):String{
		arg = String(arg);
		return arg.substring(arg.lastIndexOf("http://"));
	 }

}
